/*
 * Copyright 2008-2009 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.hasor.integration.satoken.binder;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.servlet.SaTokenServlet;
import net.hasor.core.ApiBinder;
import net.hasor.core.Environment;
import net.hasor.core.MethodInterceptor;
import net.hasor.core.binder.ApiBinderWrap;
import net.hasor.core.exts.aop.Matchers;
import net.hasor.integration.satoken.SaTokenApiBinder;
import net.hasor.integration.satoken.discoverer.MappingToDiscoverer;
import net.hasor.integration.satoken.interceptor.SaCheckInterceptor;
import net.hasor.integration.satoken.interceptor.SaCheckInvokerFilter;
import net.hasor.integration.satoken.tools.SaTokenServletForHasor;
import net.hasor.web.WebApiBinder;
import net.hasor.web.spi.MappingDiscoverer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.function.Predicate;

import static net.hasor.core.HasorUtils.autoAware;

/**
 * SaTokenApiBinder 接口实现
 * @version : 2019年10月30日
 * @author 赵永春 (zyc@hasor.net)
 */
class SaTokenApiBinderImpl extends ApiBinderWrap implements SaTokenApiBinder {
    private final Environment env;

    SaTokenApiBinderImpl(ApiBinder apiBinder) {
        super(apiBinder);
        this.env = this.getEnvironment();
        apiBinder.bindType(SaTokenServlet.class, SaTokenServletForHasor.class); // 声明 SaTokenServlet 和 SaTokenServletForHasor 的实现关系，后面 AbstractSaCheck 注入的时候会根据这个关系来进行注入
        apiBinder.bindSpiListener(MappingDiscoverer.class, new MappingToDiscoverer()); // DEMO
    }

    protected WebApiBinder webApiBinder() {
        return this.tryCast(WebApiBinder.class);
    }

    @Override
    public SaTokenApiBinder enableAnnotation() {//
        // 注册 SA-TOKEN 注解拦截器（由于目前 bindInterceptor 还不支持 Class 的方式，因此只能通过 autoAware 迂回实现依赖注入）
        initSaTokenInterceptor(webApiBinder(), new Class[] { SaCheckLogin.class, SaCheckPermission.class, SaCheckRole.class }, autoAware(env, new SaCheckInterceptor()));
        return this;
    }

    protected void initSaTokenInterceptor(WebApiBinder apiBinder, Class<? extends Annotation>[] annoTypes, MethodInterceptor interceptor) {
        // 1. 类上标记了 annoTypes 中任意一个注解
        Predicate<Class<?>> withClass = null;
        for (Class<? extends Annotation> type : annoTypes) {
            if (withClass == null) {
                withClass = Matchers.annotatedWithClass(type);
            } else {
                withClass = withClass.or(Matchers.annotatedWithClass(type));
            }
        }
        // 2. 方法上标记了 annoTypes 中任意一个注解
        Predicate<Method> withMethod = null;
        for (Class<? extends Annotation> type : annoTypes) {
            if (withMethod == null) {
                withMethod = Matchers.annotatedWithMethod(type);
            } else {
                withMethod = withMethod.or(Matchers.annotatedWithMethod(type));
            }
        }
        // 3. 把 withClass 整合进 withMethod
        Predicate<Class<?>> finalWithClass = withClass;
        withMethod = withMethod.or(method -> {
            return finalWithClass.test(method.getDeclaringClass());
        });
        //
        // 4. 任意类或方法上标注了 anno 注解的，全部走 interceptor 拦截器。（通过 method 来判断）
        apiBinder.bindInterceptor(Matchers.anyClass(), withMethod, interceptor);
    }

    @Override
    public SaTokenApiBinder enableWebInterceptor(int index) {
        // 注册拦截器，Web 拦截器，效果等同于 Filter
        webApiBinder().filter("/*").through(index, autoAware(env, new SaCheckInvokerFilter()));
        return this;
    }

    @Override
    public SaTokenApiBinder setConfig(SaTokenConfig saTokenConfig) {
        SaTokenManager.setConfig(saTokenConfig);
        return this;
    }
}
